﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Hosting;
using UDS.Components;

namespace UDS.Entity
{
    /// <summary>
    /// Specifies the current status of a document.
    /// </summary>
    public enum DocumentStatus
    {
        /// <summary>
        /// 0: 未签
        /// </summary>
        [DisplayName("未签")]
        None = 0,

        /// <summary>
        /// 1: 待批
        /// </summary>
        [DisplayName("待批")]
        Pending = 1,

        /// <summary>
        /// 2: 会签中
        /// </summary>
        [DisplayName("会签中")]
        InReview = 2,

        /// <summary>
        /// 3: 完成
        /// </summary>
        [DisplayName("完成")]
        Finished = 3,

        /// <summary>
        /// 4: 拒绝
        /// </summary>
        [DisplayName("拒绝")]
        Rejected = 4,
    }

    /// <summary>
    /// 指定用户对文档的操作状态
    /// </summary>
    public enum DocumentAction
    {
        /// <summary>
        /// 0: 签收
        /// </summary>
        [DisplayName("签收")]
        Receive = 0,

        /// <summary>
        /// 1: 一般待批
        /// </summary>
        [DisplayName("一般待批")]
        Pending = 1,

        /// <summary>
        /// 2: 撤回
        /// </summary>
        [DisplayName("撤回")]
        Reject = 2,

        /// <summary>
        /// 3: 查看
        /// </summary>
        [DisplayName("查看")]
        View = 3,

        /// <summary>
        /// 4: 会签待批
        /// </summary>
        [DisplayName("会签待批")]
        CounterSignPending = 4,
    }

    /// <summary>
    /// Describes 
    /// </summary>
    public class DocumentFlowStep : FlowStep
    {
        public int Order
        {
            get;
            private set;
        }

        internal static DocumentFlowStep ReadDocumentFlowStep(IDataReader dr)
        {
            DocumentFlowStep s = new DocumentFlowStep();

            ReadFlowStep(dr, s);
            s.Order = Convert.ToInt32(dr["Order_ID"]);

            return s;
        }
    }

    public class DocumentInfo : DocumentBriefInfo
    {
        #region properties
#if false
        public int DocID
        {
            get;
            set;
        }

        public DateTime TimeCreated
        {
            get;
            set;
        }

        public DocumentStatus Status
        {
            get;
            set;
        }

        public int FlowID
        {
            get;
            set;
        }

        public string FlowName
        {
            get;
            private set;
        }

        public int StepID
        {
            get;
            set;
        }

        public string StepName
        {
            get;
            private set;
        }

        /// <summary>
        /// Get or set the ID of the creator.
        /// </summary>
        public int CreatorID
        {
            get;
            set;
        }

        /// <summary>
        /// Get or set the name of the creator.
        /// </summary>
        public string Creator
        {
            get;
            set;
        }

        /// <summary>
        /// Get or set the display name of the creator.
        /// </summary>
        public string CreatorRealName
        {
            get;
            set;
        }
#endif

        /// <summary>
        /// Get the title of this document.
        /// </summary>
        public override string Title
        {
            get { return Data.A; }
            protected set { Data.A = value; }
        }

        public FlowStyleData Data
        {
            get;
            set;
        }
        #endregion properties

        /// <summary>
        /// Get the detailed information about the document.
        /// </summary>
        public static DocumentInfo GetDocumentInfo(int docId)
        {
            var db = new Database();
            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            using (var dr = db.RunDataReader("dbo.sproc_Flow_GetDocumentInfo_V2", parameters))
            {
                if (dr.Read())
                {
                    return ReadDocumentInfo(dr, docId);
                }
            }

            return null;
        }

        /// <summary>
        /// 获得文档的操作状态
        /// </summary>
        public static DocumentAction GetDocumentStatus(int DocID, string userName)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@StaffName", SqlDbType.NVarChar, 300, userName),
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, DocID)
            };

            using (var db = new Database())
            {
                return (DocumentAction)db.RunProc("dbo.sproc_Flow_GetDocumentStatusByStaff_V2", parameters);
            }
        }

        /// <summary>
        /// 获得是否按照项目流转
        /// </summary>
        /// <param name="UserName">The name of the current user.</param>
        /// <param name="DocID">The document id.</param>
        public static bool IsFlowByProject(string UserName, int DocID)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@StaffName", SqlDbType.NVarChar, 50, UserName),
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, DocID)
            };

            var db = new Database();
            using (var dr = db.RunDataReader("dbo.sproc_Flow_GetPrePostInfo_V3", parameters))
            {
                if (dr.Read())
                {
                    if ((int)dr["Flow_Rule"] == (int)FlowRule.Project)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// 签收文档
        /// </summary>
        /// <param name="UserName">签收人</param>
        /// <param name="DocID">要被签收的文档ID</param>
        public static int SignInDocument(string userName, int docId)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@StaffName", SqlDbType.NVarChar, 300, userName),
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            var db = new Database();
            return db.RunProc("dbo.sproc_Flow_SignINDoc_V2", parameters);
        }

        /// <summary>
        /// 取消签收
        /// </summary>
        /// <param name="UserName">签收人</param>
        /// <param name="DocID">被取消的文档ID</param>
        public static int CancelSignInDocument(string userName, int docId)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@StaffName", SqlDbType.NVarChar, 300, userName),
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            var db = new Database();
            return db.RunProc("dbo.sproc_Flow_CancelSignINDoc_V2", parameters);
        }

        /// <summary>
        /// 退回文档
        /// </summary>
        /// <param name="docId">被退回的文档ID</param>
        /// <param name="toOrderId">The order id of the step to return (or -1 to previous step)</param>
        public static void BackDocument(string userName, int docId, string postilContent, int ObjID, int ObjType, HttpPostedFile postedFile, int toOrderId)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId),
                Database.MakeInParam("@NextOrder", SqlDbType.Int, 4, toOrderId)
            };

            using (var db = new Database())
            {
                db.BeginTransaction();

                // Create a postil first
                int postilId = DocumentPostil.AddPostil(db, userName, docId, postilContent, PostilType.Fallback, ObjID, ObjType);
                db.RunProc("dbo.sproc_Flow_BackDocument_V3", parameters);

                if (postedFile != null)
                {
                    SaveUploadedFile(db, docId, userName, postedFile, postilId);
                }

                // Finally commit the transaction
                db.Commit();
            }
        }

        /// <summary>
        /// 否决文档
        /// </summary>
        /// <param name="docId">被否决的文档ID</param>
        public static void FinishDocument(string userName, int docId, string postilContent, int ObjID, int ObjType, HttpPostedFile postedFile)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            using (var db = new Database())
            {
                db.BeginTransaction();

                // Create a postil first
                int postilId = DocumentPostil.AddPostil(db, userName, docId, postilContent, PostilType.Complete, ObjID, ObjType);
                db.RunProc("dbo.sproc_Flow_FinishDocument_V2", parameters);

                if (postedFile != null)
                {
                    SaveUploadedFile(db, docId, userName, postedFile, postilId);
                }

                // Finally commit the transaction
                db.Commit();
            }
        }

        /// <summary>
        /// 否决文档
        /// </summary>
        /// <param name="docId">被否决的文档ID</param>
        public static void RejectDocument(string userName, int docId, string postilContent, int ObjID, int ObjType, HttpPostedFile postedFile)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            using (var db = new Database())
            {
                db.BeginTransaction();

                // Create a postil first
                int postilId = DocumentPostil.AddPostil(db, userName, docId, postilContent, PostilType.Reject, ObjID, ObjType);
                db.RunProc("dbo.sproc_Flow_RejectDocument_V2", parameters);

                if (postedFile != null)
                {
                    SaveUploadedFile(db, docId, userName, postedFile, postilId);
                }

                // Finally commit the transaction
                db.Commit();
            }
        }

        /// <summary>
        /// 通过文档
        /// </summary>
        /// <param name="userName">审批人</param>
        /// <param name="docId">当前文档ID</param>		
        /// <param name="projectId">所在项目ID</param>
        public static int PostDocument(string userName, int docId, int projectId)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@StaffName", SqlDbType.NVarChar, 300, userName),
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId),
                Database.MakeInParam("@ProjectID", SqlDbType.Int, 4, projectId)
            };

            using (var db = new Database())
            {
                db.BeginTransaction();

                // Post the document
                int returnValue = db.RunProc("dbo.sproc_Flow_PostDocument_V4", parameters);

                // Finally commit the transaction
                db.Commit();
                return returnValue;
            }
        }

        /// <summary>
        /// 通过文档
        /// </summary>
        /// <param name="userName">审批人</param>
        /// <param name="docId">当前文档ID</param>		
        /// <param name="projectId">所在项目ID</param>
        public static int PostDocument(string userName, int docId, int projectId, string postilContent, int ObjType, HttpPostedFile postedFile)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@StaffName", SqlDbType.NVarChar, 300, userName),
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId),
                Database.MakeInParam("@ProjectID", SqlDbType.Int, 4, projectId)
            };

            using (var db = new Database())
            {
                db.BeginTransaction();

                // Create a postil
                int postilId = DocumentPostil.AddPostil(db, userName, docId, postilContent, PostilType.Approve, projectId, ObjType);

                // Post the document
                int returnValue = db.RunProc("dbo.sproc_Flow_PostDocument_V4", parameters);

                // Save the attachments
                if (postedFile != null)
                {
                    SaveUploadedFile(db, docId, userName, postedFile, postilId);
                }

                // Finally commit the transaction
                db.Commit();
                return returnValue;
            }
        }

        /// <summary>
        /// 获得流程的表格样式描述
        /// </summary>
        /// <param name="ErrorNum">错误消息</param>		
        /// <param name="docId">文档ID</param>
        /// <param name="DefaultOperation">是否应用默认操作（比如删除错误的文档等）</param>
        public static string GetErrorMessage(int ErrorNum, int docId, bool DefaultOperation)
        {
            string errMsg;
            switch (ErrorNum)
            {
                case 0:
                    errMsg = "成功";
                    break;

                case -1:
                    errMsg = "流程结束！";
                    break;

                case -2:
                    if (DefaultOperation)
                    {
                        if (IsNewDocument(docId))
                        {
                            DocumentFlow.DeleteDocument(docId);
                        }
                        else
                        {
                            DocumentPostil.CancelLatestPostil(docId);
                        }
                    }
                    errMsg = "没有职位上级";
                    break;

                case -3:
                    if (DefaultOperation)
                    {
                        if (IsNewDocument(docId))
                        {
                            DocumentFlow.DeleteDocument(docId);
                        }
                        else
                        {
                            DocumentPostil.CancelLatestPostil(docId);
                        }
                    }
                    errMsg = "没有项目上级";
                    break;

                case -4:
                    errMsg = "需要全体通过";
                    break;

                case -5:
                    errMsg = "通过人数不够";
                    break;

                case -6:
                    if (DefaultOperation)
                    {
                        if (IsNewDocument(docId))
                        {
                            DocumentFlow.DeleteDocument(docId);
                        }
                        else
                        {
                            DocumentPostil.CancelLatestPostil(docId);
                        }
                    }
                    errMsg = "该流程无你所在的环节";
                    break;

                case -7:
                    errMsg = "下一步骤没有成员";
                    break;

                default:
                    errMsg = "操作错误";
                    break;
            }
            return errMsg;
        }

        /// <summary>
        /// 添加文档
        /// </summary>
        /// <param name="userName">拟稿人</param>
        /// <param name="FlowID">所用流程ID</param>
        /// <param name="SQL">样式表数据的SQL语句</param>
        public static int AddDocument(string userName, int FlowID, string[] styleDataNames, string[] styleDataValues, HttpPostedFile postedFile)
        {
            using (var db = new Database())
            {
                db.BeginTransaction();

                // Insert data to table UDS_Flow_Style_Data.
                int docId = InsertFlowStyleData(db, styleDataNames, styleDataValues);
                if (docId <= 0)
                {
                    throw new InvalidOperationException("Failed to create a new document.");
                }

                // Insert data to table UDS_Flow_Document
                var parameters = new[]
                {
                    Database.MakeInParam("@DocBuilder", SqlDbType.NVarChar, 50, userName),
                    Database.MakeInParam("@FlowID", SqlDbType.Int, 4, FlowID),
                    Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
                };
                db.RunProc("dbo.sproc_Flow_AddDocument_V2", parameters);

                // Save attachment file
                SaveUploadedFile(db, docId, userName, postedFile, -1);

                // Finally commits the transaction
                db.Commit();
                return docId;
            }
        }

        /// <summary>
        /// 修改文档
        /// </summary>
        public static void UpdateDocument(int docId, string userName, string[] columns, string[] values, HttpPostedFile postedFile)
        {
            using (var db = new Database())
            {
                db.BeginTransaction();

                UpdateFlowStyleData(db, docId, columns, values);

                // Get the original attachment file
                string filePath = DocumentFile.GetAttachmentPath(docId, db);
                if (!string.IsNullOrEmpty(filePath))
                {
                    // Remove the attachment record for the document first
                    DeleteAttach(db, docId);
                }

                SaveUploadedFile(db, docId, userName, postedFile, -1);

                // Remove the old attachment
                if (!string.IsNullOrEmpty(filePath))
                {
                    DocumentFlow.DeleteLocalAttachFile(filePath, true);
                }

                // Finally commits the transaction
                db.Commit();
            }
        }

        public static DocumentFlowStep[] GetDocumentPath(int docId)
        {
            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId),
            };

            using (var db = new Database())
            {
                using (var dr = db.RunDataReader("dbo.sproc_Flow_GetDocumentPath_V3", parameters))
                {
                    List<DocumentFlowStep> list = new List<DocumentFlowStep>(8);
                    while (dr.Read())
                    {
                        list.Add(DocumentFlowStep.ReadDocumentFlowStep(dr));
                    }

                    return list.ToArray();
                }
            }
        }

        #region private methods

        private static DocumentFile SaveUploadedFile(Database db, int docId, string userName, HttpPostedFile postedFile, int postilId)
        {
            if (postedFile == null)
            {
                return null;
            }

            string fileName = Path.GetFileName(postedFile.FileName);
            if (!string.IsNullOrEmpty(fileName))
            {
                // 生成用户目录
                var userDirVPath = Utility.CombineVirtualDirs(DocAttachFile.AttachVDir, userName);
                var userDir = new DirectoryInfo(HostingEnvironment.MapPath(userDirVPath));
                if (!userDir.Exists)
                {
                    userDir.Create();
                }

                // 生成随机目录
                string NewDocDirName = string.Format("{0:0000}_{1:yyyyMMddHHmmss}_{2:0000}", docId, DateTime.Now, Utility.GetRandomNumber(10000));
                var tmpDirVPath = Utility.CombineVirtualDirs(userDirVPath, NewDocDirName);
                var tmpDir = new DirectoryInfo(HostingEnvironment.MapPath(tmpDirVPath));
                if (!tmpDir.Exists)
                {
                    tmpDir.Create();
                }

                // Create the file in DB
                var file = new DocumentFile();
                file.DocID = docId;
                file.FileVisualPath = tmpDirVPath;
                file.FileName = fileName;
                file.FileAttribute = 0;
                file.FileSize = postedFile.ContentLength;
                file.Author = userName;
                file.Catalog = "公文";
                file.TimeCreated = DateTime.Now;
                if (postilId > 0)
                {
                    DocumentFile.AddPostilAttach(db, file, postilId);
                }
                else
                {
                    DocumentFile.AddAttach(db, file);
                }

                // Store the file to the local disk storage
                postedFile.SaveAs(Path.Combine(tmpDir.FullName, fileName));
                return file;
            }

            return null;
        }

        private static int InsertFlowStyleData(Database db, string[] columns, string[] values)
        {
            if (columns == null || columns.Length == 0)
            {
                throw new ArgumentNullException("columns");
            }
            if (values == null || values.Length == 0)
            {
                throw new ArgumentNullException("values");
            }
            if (columns.Length != values.Length)
            {
                throw new ArgumentException("The length of the 2 arrays must be equal.", "values");
            }

            // var sql = new StringBuilder();
            var columnClause = new StringBuilder();
            var valueClause = new StringBuilder();
            var parameters = new List<SqlParameter>(columns.Length);
            var firstParam = true;
            var numColumns = 0;

            columnClause.Append("INSERT INTO dbo.UDS_Flow_Style_Data (");
            valueClause.Append(") VALUES (");
            for (var i = 0; i < columns.Length; i++)
            {
                var columnName = columns[i];
                if (string.IsNullOrEmpty(columnName))
                {
                    // We'll ignore empty column names.
                    continue;
                }

                // Is the column name valid?
                // A valid column name is a single-character string from 'a' to 'z'.
                columnName = columnName.ToLower();
                if (columnName.Length != 1 || columnName[0] < 'a' || columnName[0] > 'z')
                {
                    throw new ArgumentException(string.Format("Invalid column name: '{0}'.", columnName), "columns");
                }

                if (!firstParam)
                {
                    columnClause.Append(", ");
                    valueClause.Append(", ");
                }
                else
                {
                    firstParam = false;
                }

                columnClause.Append(columnName);
                valueClause.Append("@" + columnName);
                parameters.Add(Database.MakeInParam("@" + columnName, SqlDbType.NVarChar, 1000, values[i]));
                numColumns++;
            }
            valueClause.AppendLine(");");
            valueClause.AppendLine("SELECT @@IDENTITY;");

            if (numColumns == 0)
            {
                throw new ArgumentException("No valid columns are specified.", "columns");
            }
            using (var dr = db.RunSQLText(columnClause.ToString() + valueClause.ToString(), parameters))
            {
                if (dr.Read())
                {
                    return Convert.ToInt32(dr[0]);
                }
            }

            return -1;
        }

        private static void UpdateFlowStyleData(Database db, int docId, string[] columns, string[] values)
        {
            if (columns == null || columns.Length == 0 || values == null || values.Length == 0)
            {
                throw new ArgumentException("No data is provider for the document");
            }
            if (columns.Length != values.Length)
            {
                throw new ArgumentException("The length of the 2 arrays must be equal.", "values");
            }

            var dict = new Dictionary<char, string>();
            for (var i = 0; i < columns.Length; i++)
            {
                var columnName = columns[i];
                if (string.IsNullOrEmpty(columnName))
                {
                    // We'll ignore empty column names.
                    continue;
                }

                // Is the column name valid?
                // A valid column name is a single-character string from 'a' to 'z'.
                if (columnName.Length != 1)
                {
                    throw new ArgumentException(string.Format("Invalid column name: '{0}'.", columnName), "columns");
                }
                char ch = char.ToUpper(columnName[0]);
                if (ch < 'A' || ch > 'Z')
                {
                    throw new ArgumentException(string.Format("Invalid column name: '{0}'.", columnName), "columns");
                }

                dict.Add(ch, values[i]);
            }
            if (dict.Count == 0)
            {
                throw new ArgumentException("No data is provider for the document");
            }

            var sql = new StringBuilder(1024);
            var parameters = new List<SqlParameter>(columns.Length);

            sql.Append("UPDATE dbo.UDS_Flow_Style_Data SET ");
            for (char ch = 'A'; ch <= 'Z'; ch++)
            {
                if (ch != 'A')
                {
                    sql.Append(',');
                }

                sql.Append(ch);
                sql.Append('=');
                if (dict.ContainsKey(ch))
                {
                    sql.Append('@');
                    sql.Append(ch);

                    parameters.Add(Database.MakeInParam("@" + ch, SqlDbType.NVarChar, 1000, dict[ch]));
                }
                else
                {
                    sql.Append("NULL");
                }
            }

            // The WHERE clause
            sql.AppendFormat(" WHERE Doc_ID = {0};", docId);

            db.RunSQLText2(sql.ToString(), parameters);
        }

        /// <summary>
        /// 删除文档附件
        /// </summary>
        /// <param name="db">The database connection</param>
        /// <param name="docId">文档ID</param>
        private static void DeleteAttach(Database db, int docId)
        {
            Debug.Assert(db != null);
            Debug.Assert(db.IsInTransaction);
            Debug.Assert(docId > 0);

            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            db.RunProc("dbo.sproc_Flow_DeleteFile_V2", parameters);
        }

        /// <summary>
        /// Determines whether the document is a new one (IsRunning = 0);
        /// </summary>
        private static bool IsNewDocument(int docId)
        {
            Debug.Assert(docId > 0);

            var parameters = new[]
            {
                Database.MakeInParam("@DocID", SqlDbType.Int, 4, docId)
            };

            using (var db = new Database())
            {
                int r = db.RunProc("dbo.sproc_Flow_IsNewDocument_V2", parameters);
                return (r == 1);
            }
        }

        private static DocumentInfo ReadDocumentInfo(IDataReader dr, int docId)
        {
            var d = new DocumentInfo();
            d.DocID = docId;

            ReadDocumentBriefInfo(dr, d);
            d.Data = FlowStyleData.ReadFlowStyleData(dr, docId);
            return d;
        }

        #endregion private methods
    }
}
